import typing
from selenium.webdriver.edge.webdriver import WebDriver
from selenium.webdriver.edge.options import Options
from selenium.webdriver.edge.service import Service
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.common.by import By


# =============================================================================
# 函数用法通俗介绍
# =============================================================================
# 
# 本文件包含一个主要的元素查找函数，它有多种查找策略：
# 
# 1. find_element_smart - "智能查找元素"
#    这个函数有多种查找模式：
#    - 兄弟模式（siblings）：只在同一级别的元素中查找（就像找你的兄弟姐妹），不会查找兄弟元素的子元素
#    - 父亲模式（parent）：在父元素内部查找所有后代元素（不仅限于直接子元素，而是整个子树）
#    - 祖先模式（ancestors）：在所有长辈元素内部查找（就像在爷爷、父亲家里都找找）
#    - 层级搜索模式（hierarchy）：从当前元素开始逐层向上查找
#
# HTML结构示例：
# <div class="form-container">
#   <div class="form-group">
#     <label for="username">用户名</label>
#     <input type="text" id="username" placeholder="请输入用户名">
#   </div>
#   <div class="form-group">
#     <label for="password">密码</label>
#     <input type="password" id="password" placeholder="请输入密码">
#     <span class="error-msg">密码不能为空</span>
#   </div>
#   <div class="button-group">
#     <button type="submit" class="btn primary">登录</button>
#     <button type="reset" class="btn secondary">重置</button>
#   </div>
# </div>
#
# 使用场景示例：
# 1. 兄弟模式：
#    - 场景：已知label元素，需要找到对应的input元素
#    - 用法：以<label for="username">为参考点，在其兄弟元素中查找<input>标签（注意：只查找与label同级的input元素，不查找兄弟元素的子元素）
#    - 代码：find_element_smart(driver, label_element, "input", {"type": "text"}, "siblings")
#
# 2. 父亲模式：
#    - 场景：已知input元素，需要找到同一容器内的错误信息
#    - 用法：以<input type="password">为参考点，在其父元素<div class="form-group">内查找所有后代元素中的<span class="error-msg">
#    - 代码：find_element_smart(driver, input_element, "span", {"class": {"type": "contains", "value": "error"}}, "parent")
#
# 3. 祖先模式：
#    - 场景：已知input元素，需要找到整个表单容器
#    - 用法：以<input type="text">为参考点，在其祖先元素中查找<div class="form-container">
#    - 代码：find_element_smart(driver, input_element, "div", {"class": "form-container"}, "ancestors")
#    - 工作原理：从锚点元素开始，逐级向上查找祖先元素（父元素、父元素的父元素...），直到找到匹配的目标元素或者达到最大查找距离为止
#    - 特点：只会检查锚点元素的直接祖先链上的元素，不会查找祖先元素的其他分支后代元素
#      例如：对于结构 <div><form><div><input></div><button></button></form></div>
#      以<input>为锚点使用祖先模式查找<button>是找不到的，因为<button>虽然和<input>有共同祖先<form>，
#      但它不是<input>的直接祖先，而是同级祖先的另一个后代
#
# 4. 层级搜索模式：
#    - 场景：已知label元素，需要在其内部或向上查找input元素
#    - 用法：以<label for="password">为参考点，逐层向上查找<input>标签
#    - 代码：find_element_smart(driver, label_element, "input", {"type": "password"}, "hierarchy")
#
# 简单选择指南：
# - 如果你知道目标元素就在锚点元素的旁边（同一级别），用兄弟模式
# - 如果你知道目标元素在锚点元素的父容器内（任意后代元素），用父亲模式
# - 如果你不确定目标元素在哪，但知道它应该在锚点元素的某个上级容器内，用祖先模式
# - 如果你需要从内向外逐层查找，用层级搜索模式
# 
# 注意事项：
# - 兄弟模式（siblings）只会查找与锚点元素处于同一层级的兄弟元素，不会查找兄弟元素的子元素
#   例如：<div><span>A</span></div><p>B</p> 中，以<span>为锚点查找<p>元素可以找到，
#   但查找<p>元素的子元素（如<p><b>B</b></p>中的<b>元素）则找不到
# - 父亲模式（parent）会在父元素的所有后代元素中查找，不仅仅是直接子元素
#   例如：<div><p><span>text</span></p></div> 中，以<span>为锚点使用父亲模式查找<p>元素可以找到
# - 祖先模式（ancestors）会逐级向上查找所有祖先元素并在其中查找目标元素
#   例如：<div class="container"><form><div class="group"><input></div></form></div> 中，
#   以<input>为锚点使用祖先模式查找<div class="container">可以找到
#   重要：祖先模式只查找直接祖先链上的元素，不会查找祖先元素的其他分支后代
#   如上述例子中，如果要查找<div class="group">元素的兄弟元素，则无法通过<input>的祖先模式查找到
# 
# 示例：
# - 找用户名输入框：用层级搜索模式，从用户名标签开始向上找
# - 找同一行的删除按钮：用兄弟模式
# - 找同一个div容器内的提交按钮：用父亲模式
# - 找页面顶层的表单容器：用祖先模式
# =============================================================================


def find_element_smart(
    driver: WebDriver,
    source_element: typing.Union[WebElement, str, None] = None,
    target_element: str = "*",
    target_attributes: typing.Union[typing.Dict[str, typing.Union[str, typing.Dict[str, str]]], None] = None,
    search_radius: typing.Literal["siblings", "parent", "ancestors", "hierarchy"] = "siblings",
    max_distance: int = 3,
    max_depth: int = 5
) -> typing.Optional[WebElement]:
    """在指定元素附近查找目标元素（抽象通用版）

    该函数提供了灵活的查找机制，可以在指定参考元素的不同范围内查找目标元素
    支持多种搜索范围和属性匹配方式

    Args:
        driver: WebDriver实例，用于执行查找操作
        source_element: 起始参考元素，支持三种类型：
            - WebElement对象：直接使用该元素作为参考点
            - 字符串：作为XPath表达式查找参考元素
            - None：在全局范围内查找目标元素
        target_element: 目标元素的标签名，如'input', 'button', 'div'等
        target_attributes: 目标元素的属性匹配条件，支持多种匹配方式：
            - 字符串：精确匹配属性值
            - 字典：指定匹配方式，支持以下类型：
                {'type': 'exists'} - 属性存在即可
                {'type': 'contains', 'value': 'partial'} - 属性包含指定值
                {'type': 'starts-with', 'value': 'prefix'} - 属性以指定值开头
                {'type': 'ends-with', 'value': 'suffix'} - 属性以指定值结尾
        search_radius: 搜索范围，可选值：
            - 'siblings': 在兄弟元素中查找（仅查找与锚点元素同级的元素，不会查找兄弟元素的后代节点）
            - 'parent': 在父元素内查找（在直接父元素的所有后代元素中查找）
            - 'ancestors': 在祖先元素内查找（逐级向上查找父元素、父元素的父元素...，仅限直接祖先链）
            - 'hierarchy': 从当前元素开始逐层向上查找
        max_distance: 最大搜索距离：
            - 对于'siblings'：表示最多查找几个兄弟元素
            - 对于'parent'：固定为1，只在直接父元素中查找
            - 对于'ancestors'：表示向上查找的祖先元素层数
        max_depth: 最大向上查找的父元素层级数（仅在hierarchy模式下使用）

    Returns:
        WebElement: 找到的目标元素，如果未找到则返回None

    Raises:
        Exception: 在查找过程中可能出现的各种异常

    Example:
        # 使用WebElement对象作为参考元素
        reference_element = driver.find_element(By.ID, "username_label")
        username_input = find_element_smart(
            driver=driver,
            source_element=reference_element,
            target_element="input",
            target_attributes={"type": "text"},
            search_radius="siblings"
        )

        # 使用XPath字符串作为参考元素
        username_input = find_element_smart(
            driver=driver,
            source_element="//label[text()='用户名']",
            target_element="input",
            target_attributes={"type": "text"},
            search_radius="siblings"
        )

        # 不指定参考元素（全局查找）
        submit_button = find_element_smart(
            driver=driver,
            source_element=None,
            target_element="button",
            target_attributes={"type": "submit"}
        )
        
        # 使用层级搜索模式
        target_element = find_element_smart(
            driver=driver,
            source_element=reference_element,
            target_element="button",
            target_attributes={"class": "submit"},
            search_radius="hierarchy",
            max_depth=3
        )
        
        # 使用祖先模式查找
        form_container = find_element_smart(
            driver=driver,
            source_element=input_element,
            target_element="div",
            target_attributes={"class": "form-container"},
            search_radius="ancestors",
            max_distance=5
        )
    """
    if target_attributes is None:
        target_attributes = {}

    # 构建目标元素的XPath条件
    conditions = []
    
    # 类型检查：确保target_attributes是字典类型
    if not isinstance(target_attributes, dict):
        print(f"错误: target_attributes参数类型错误，预期是字典类型，实际是{type(target_attributes).__name__}")
        return None
    
    for attr, spec in target_attributes.items():
        # 处理不同的属性匹配方式
        if isinstance(spec, dict):
            match_type = spec.get('type', 'equals')
            value = spec.get('value', '')
            
            if attr.lower() in ["text", "innertext"]:
                # 文本属性特殊处理
                if match_type == "exists":
                    conditions.append("text()")
                elif match_type == "contains":
                    conditions.append(f"contains(text(), '{value}')")
                elif match_type == "starts-with":
                    conditions.append(f"starts-with(text(), '{value}')")
                elif match_type == "ends-with":
                    # XPath 1.0 没有ends-with函数，使用substring模拟
                    conditions.append(f"substring(text(), string-length(text())-string-length('{value}')+1) = '{value}'")
                else:  # equals
                    conditions.append(f"text()='{value}'")
            else:
                # 普通属性处理
                if match_type == "exists":
                    conditions.append(f"@{attr}")
                elif match_type == "contains":
                    conditions.append(f"contains(@{attr}, '{value}')")
                elif match_type == "starts-with":
                    conditions.append(f"starts-with(@{attr}, '{value}')")
                elif match_type == "ends-with":
                    # XPath 1.0 没有ends-with函数，使用substring模拟
                    conditions.append(f"substring(@{attr}, string-length(@{attr})-string-length('{value}')+1) = '{value}'")
                else:  # equals
                    conditions.append(f"@{attr}='{value}'")
        else:
            # 简写形式：直接提供字符串表示精确匹配
            if attr.lower() in ["text", "innertext"]:
                conditions.append(f"text()='{spec}'")
            else:
                conditions.append(f"@{attr}='{spec}'")

    target_condition = " and ".join(conditions)
    target_xpath = target_element + (f"[{target_condition}]" if conditions else "")

    # 处理source_element参数
    actual_source_element = None
    
    if source_element is None:
        # 如果没有指定参考元素，则在全局范围内查找目标元素
        try:
            global_elements = driver.find_elements(By.XPATH, f"//{target_xpath}")
            if global_elements:
                # 直接返回第一个找到的元素
                return global_elements[0]
        except Exception as e:
            print(f"全局查找过程中出错: {str(e)}")
        return None
    elif isinstance(source_element, str):
        # 如果source_element是字符串，尝试作为XPath查找元素
        try:
            elements = driver.find_elements(By.XPATH, source_element)
            if elements:
                actual_source_element = elements[0]
            else:
                print(f"警告: 无法找到XPath为 '{source_element}' 的元素")
                return None
        except Exception as e:
            print(f"通过XPath查找元素时出错: {str(e)}")
            return None
    else:
        # 如果是WebElement对象，直接使用
        actual_source_element = source_element

    # 查找附近的目标元素
    candidates = []
    try:
        if search_radius == "siblings":
            # 查找兄弟元素（仅查找与锚点元素同级的元素，不会查找兄弟元素的后代节点）
            for distance in range(1, max_distance + 1):
                # 查找后续兄弟
                following_xpath = f"./following-sibling::*[position()<={distance}]/{target_xpath}"
                following_elements = actual_source_element.find_elements(
                    By.XPATH, following_xpath)

                # 查找前驱兄弟
                preceding_xpath = f"./preceding-sibling::*[position()<={distance}]/{target_xpath}"
                preceding_elements = actual_source_element.find_elements(
                    By.XPATH, preceding_xpath)

                if following_elements:
                    candidates.append((following_elements[0], distance))
                if preceding_elements:
                    candidates.append((preceding_elements[0], distance))

                if following_elements or preceding_elements:
                    break

        elif search_radius in ["parent", "ancestors"]:
            current_element = actual_source_element
            
            # 遍历父元素或祖先元素
            for depth in range(1, max_distance + 1):
                # 向上查找父级或祖先
                if search_radius == "parent" and depth > 1:
                    break

                try:
                    # 查找父元素
                    parent = current_element.find_element(
                        By.XPATH, "./parent::*")
                except Exception as e:
                    print(f"无法获取父元素: {str(e)}")
                    break

                # 在父元素中查找目标元素
                elements_in_parent = parent.find_elements(
                    By.XPATH, f".//{target_xpath}")

                if elements_in_parent:
                    # 计算元素在父级中的位置距离
                    all_children = parent.find_elements(By.XPATH, ".//*")
                    source_index = all_children.index(
                        actual_source_element) if actual_source_element in all_children else -1

                    for element in elements_in_parent:
                        if element in all_children:
                            element_index = all_children.index(element)
                            # 使用深度作为距离，更准确地反映查找层级
                            distance = depth
                            candidates.append((element, distance))

                # 更新当前元素为父元素，继续向上查找
                if search_radius == "ancestors":
                    current_element = parent
                else:
                    break
                    
        elif search_radius == "hierarchy":
            # 逐级向上查找目标元素（原来的find_element_hierarchy_search功能）
            current_element = actual_source_element
            found_element = None
            
            for depth in range(max_depth + 1):
                try:
                    # 在当前元素内查找目标元素
                    target_elements = current_element.find_elements(
                        By.XPATH, f".//{target_xpath}")
                    
                    if target_elements:
                        # 返回找到的第一个目标元素
                        found_element = target_elements[0]
                        break
                    
                    # 如果未找到，尝试获取父元素继续查找
                    if depth < max_depth:  # 避免在最后一层尝试获取父元素
                        parent = current_element.find_element(By.XPATH, "./parent::*")
                        current_element = parent
                except Exception as e:
                    print(f"层级查找过程中出错 (深度={depth}): {str(e)}")
                    break
            
            if found_element:
                return found_element
            else:
                print(f"警告: 在指定元素及其祖先元素（最大深度={max_depth}）中未找到目标元素")
                return None

    except Exception as e:
        print(f"查找过程中出错: {str(e)}")

    # 如果没有找到候选元素
    if not candidates:
        print(f"警告: 在指定元素附近未找到目标元素")
        result = None
    else:
        # 选择距离最近的目标元素
        best_candidate = min(candidates, key=lambda x: x[1])
        result = best_candidate[0]
    
    return result


# 保持向后兼容性，保留原函数名
find_element_near_element = find_element_smart


if __name__ == "__main__":
    # 创建WebDriver实例
    options = Options()
    service = Service()
    # 示例：使用已有的浏览器会话进行调试
    options.add_experimental_option("debuggerAddress", "127.0.0.1:9652")
    driver = WebDriver(options=options)
    # driver.get(url="https://www.bilibili.com/")
    
    # 修复target_attributes参数，使用正确的字典格式
    
    print(driver.current_url)
    # 测试新的层级查找函数
    try:
        source_element = driver.find_element(By.XPATH, "//input[@placeholder]")
        element = find_element_near_element(
            driver=driver,
            source_element=source_element,
            target_element="*",
            target_attributes={'text': '消息'},
            search_radius="hierarchy",
            max_depth=10
        )
        print(element)
        print(element.tag_name)
        print(element.get_attribute("class"))
    except Exception as e:
        print(f"测试过程中出错: {str(e)}")
    
    # 以下是示例调用方式，实际使用时需要根据具体情况调整
    # 1. 使用WebElement对象
    # reference_element = driver.find_element(By.XPATH, "//some/xpath")
    # result = find_element_near_element(
    #     driver=driver,
    #     source_element=reference_element,
    #     target_element="button",
    #     target_attributes={"class": "submit-btn"}
    # )
    
    # 2. 使用XPath字符串
    # result = find_element_near_element(
    #     driver=driver,
    #     source_element="//label[text()='用户名']",
    #     target_element="input",
    #     target_attributes={"type": "text"}
    # )
    
    # 3. 不指定参考元素（全局查找）
    # result = find_element_near_element(
    #     driver=driver,
    #     source_element=None,
    #     target_element="button",
    #     target_attributes={"type": "submit"}
    # )